package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.service.TestService;
import jdk.nashorn.internal.runtime.URIUtils;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    //@Override
    //public synchronized void testLock() {
    //   // 查询Redis中的num值
    //   String value = (String)this.redisTemplate.opsForValue().get("num");
    //   // 没有该值return
    //   if (StringUtils.isBlank(value)){
    //      return ;
    //   }
    //   // 有值就转成成int
    //   int num = Integer.parseInt(value);
    //   // 把Redis中的num值+1
    //   this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
    //}

    /**
     * 基于SpringDataRedis实现分布式锁
     */
    //@Override
    //public void testLock() {
    //    //一.先获取分布式锁
    //    redisTemplate.opsForValue().setIfAbsent("lock", "lock");
    //    redisTemplate.expire("lock", Duration.ofSeconds(5));
    //    //问题:不同线程之家互相释放锁 导致违反了分布式锁排他性
    //    //解决:每个线程加锁是 存入Redis锁的值UUID 删除锁时候判断是否为当前线程锁
    //    String lockVal = UUID.randomUUID().toString();
    //    Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", lockVal, 5, TimeUnit.SECONDS);
    //    //TODO 问题:遗留问题 某个线程超时(业务执行时间大于锁自动释放时间)违反排他性(后续Redisson框架解决)
    //    //二.获取锁成功执行业务
    //    try {
    //        if (flag) {
    //            try {
    //                // 查询Redis中的num值
    //                String value = (String) this.redisTemplate.opsForValue().get("num");
    //                // 没有该值return
    //                if (StringUtils.isBlank(value)) {
    //                    return;
    //                }
    //                TimeUnit.SECONDS.sleep(7);
    //                // 有值就转成成int
    //                int num = Integer.parseInt(value);
    //                // 把Redis中的num值+1
    //                this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
    //            } finally {
    //                //三.主动是否锁
    //                //3.1.获取Redis中存放锁的值
    //                //String redisLockValue = redisTemplate.opsForValue().get("lock");
    //                //3.2 判断跟当前线程栈帧中局部变量lockVal是否一致
    //                //释放锁问题:判断删除不具备原子性.线程1将要释放自己的锁
    //                //if (lockVal.equals(redisLockValue)) { //判断释放条件成立
    //                //    // 线程1锁刚好过期,线程2获取锁成功.执行业务 线程1恢复,
    //                //    redisTemplate.delete("lock"); //线程1删除线程2的锁
    //                //}
    //                //解决:保证判断 删除锁原子性  lua脚本来保证
    //                //3.3.1 先创建封装Lua脚本对象
    //                String scriptText = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
    //                        "then\n" +
    //                        "    return redis.call(\"del\",KEYS[1])\n" +
    //                        "else\n" +
    //                        "    return 0\n" +
    //                        "end";
    //                DefaultRedisScript<Long> script = new DefaultRedisScript<>();
    //                script.setScriptText(scriptText);
    //                script.setResultType(Long.class);
    //                //3.3.3 执行脚本
    //                //3.3.2 设置动态参数 Key Args
    //                Long lock = redisTemplate.execute(script, Arrays.asList("lock"), lockVal);
    //                System.out.println(lock);
    //            }
    //        } else {
    //            //自旋
    //            Thread.sleep(500);
    //            this.testLock();
    //        }
    //    } catch (NumberFormatException e) {
    //        throw new RuntimeException(e);
    //    } catch (InterruptedException e) {
    //        throw new RuntimeException(e);
    //    }
    //}

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 使用Redisson框架实现分布式锁
     * 开发步骤:
     * 1.引入redisson依赖 在IOC容器中注册对象RedissonClient对象
     * 2.注入RedissonClient对象
     * 3.调用RedissonClient方法获取锁
     * 4.执行业务后将锁释放
     */
    @Override
    public void testLock() {
        //1.先获取锁对象
        RLock lock = redissonClient.getLock("lock");

        //2.获取分布式锁
        lock.lock();  //一直到获取锁为止
        //lock.tryLock(3, TimeUnit.SECONDS);  //设置获取锁最大等待时间,超过该时间返回false
        //lock.tryLock(2, 3, TimeUnit.SECONDS);  //设置获取锁最大等待时间+锁超时时机

        try {
            String value = (String) this.redisTemplate.opsForValue().get("num");
            // 没有该值return
            if (StringUtils.isBlank(value)) {
                return;
            }
            checkLock();
            // 有值就转成成int
            int num = Integer.parseInt(value);
            // 把Redis中的num值+1
            this.redisTemplate.opsForValue().set("num", String.valueOf(++num));
        } finally {
            //3.释放锁
            lock.unlock();
        }
    }

    /**
     * 读数据方法获取读锁
     * @return
     */
    @Override
    public String read() {
        //1.获取读锁
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        RLock rLock = rwLock.readLock();
        try {
            rLock.tryLock(1, 5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //2.执行读取数据业务
        try {
            //Thread.sleep(3000);
            System.out.println("进行模拟读取数据");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //3.释放读锁 todo 自动释放
        return "readValue";
    }

    /**
     * 写数据获取写锁
     */
    @Override
    public void write() {
        //1.获取写锁
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        RLock wLock = rwLock.writeLock();
        try {
            wLock.tryLock(1, 5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //2.执行写取数据业务
        try {
            //Thread.sleep(3000);
            System.out.println("进行模拟读取数据");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //3.释放写锁 todo 自动释放
    }

    private void checkLock() {
        RLock lock = redissonClient.getLock("lock");
        lock.lock();

        System.out.println("check可重入");

        lock.unlock();
    }
}
